home *** CD-ROM | disk | FTP | other *** search
Text File | 1995-03-27 | 47.5 KB | 1,054 lines | [TEXT/ROSA] |
- Common Lisp the Language, 2nd Edition
- -------------------------------------------------------------------------------
-
- 19. Structures
-
- Common Lisp provides a facility for creating named record structures with named
- components. In effect, the user can define a new data type; every data
- structure of that type has components with specified names. Constructor,
- access, and assignment constructs are automatically defined when the data type
- is defined.
-
- This chapter is divided into two parts. The first part discusses the basics of
- the structure facility, which is very simple and allows the user to take
- advantage of the type-checking, modularity, and convenience of user-defined
- record data types. The second part, beginning with section 19.5, discusses a
- number of specialized features of the facility that have advanced applications.
- These features are completely optional, and you needn't even know they exist in
- order to take advantage of the basics.
-
- -------------------------------------------------------------------------------
-
- * Introduction to Structures
- * How to Use Defstruct
- * Using the Automatically Defined Constructor Function
- * Defstruct Slot-Options
- * Defstruct Options
- * By-Position Constructor Functions
- * Structures of Explicitly Specified Representational Type
- o Unnamed Structures
- o Named Structures
- o Other Aspects of Explicitly Specified Structures
-
- -------------------------------------------------------------------------------
-
- 19.1. Introduction to Structures
-
- The structure facility is embodied in the defstruct macro, which allows the
- user to create and use aggregate data types with named elements. These are like
- ``structures'' in PL/I, or ``records'' in Pascal.
-
- As an example, assume you are writing a Lisp program that deals with space
- ships in a two-dimensional plane. In your program, you need to represent a
- space ship by a Lisp object of some kind. The interesting things about a space
- ship, as far as your program is concerned, are its position (represented as x
- and y coordinates), velocity (represented as components along the x and y
- axes), and mass.
-
- A ship might therefore be represented as a record structure with five
- components: x-position, y-position, x-velocity, y-velocity, and mass. This
- structure could in turn be implemented as a Lisp object in a number of ways. It
- could be a list of five elements; the x-position could be the car, the
- y-position the cadr, and so on. Equally well it could be a vector of five
- elements: the x-position could be element 0, the y-position element 1, and so
- on. The problem with either of these representations is that the components
- occupy places in the object that are quite arbitrary and hard to remember.
- Someone looking at (cadddr ship1) or (aref ship1 3) in a piece of code might
- find it difficult to determine that this is accessing the y-velocity component
- of ship1. Moreover, if the representation of a ship should have to be changed,
- it would be very difficult to find all the places in the code to be changed to
- match (not all occurrences of cadddr are intended to extract the y-velocity
- from a ship).
-
- Ideally components of record structures should have names. One would like to
- write something like (ship-y-velocity ship1) instead of (cadddr ship1). One
- would also like a more mnemonic way to create a ship than this:
-
- (list 0 0 0 0 0)
-
- Indeed, one would like ship to be a new data type, just like other Lisp data
- types, that one could test with typep, for example. The defstruct facility
- provides all of this.
-
- defstruct itself is a macro that defines a structure. For the space ship
- example, one might define the structure by saying:
-
- (defstruct ship
- x-position
- y-position
- x-velocity
- y-velocity
- mass)
-
- This declares that every ship is an object with five named components. The
- evaluation of this form does several things:
-
- * It defines ship-x-position to be a function of one argument, a ship, that
- returns the x-position of the ship; ship-y-position and the other
- components are given similar function definitions. These functions are
- called the access functions, as they are used to access elements of the
- structure.
-
- * The symbol ship becomes the name of a data type of which instances of
- ships are elements. This name becomes acceptable to typep, for example;
- (typep x 'ship) is true if x is a ship and false if x is any object other
- than a ship.
-
- * A function named ship-p of one argument is defined; it is a predicate
- that is true if its argument is a ship and is false otherwise.
-
- * A function called make-ship is defined that, when invoked, will create a
- data structure with five components, suitable for use with the access
- functions. Thus executing
-
- (setq ship2 (make-ship))
-
- sets ship2 to a newly created ship object. One can specify the initial
- values of any desired component in the call to make-ship by using keyword
- arguments in this way:
-
- (setq ship2 (make-ship :mass *default-ship-mass*
- :x-position 0
- :y-position 0))
-
- This constructs a new ship and initializes three of its components. This
- function is called the constructor function because it constructs a new
- structure.
-
- * The #S syntax can be used to read instances of ship structures, and a
- printer function is provided for printing out ship structures. For
- example, the value of the variable ship2 shown above might be printed as
-
- #S(ship x-position 0 y-position 0 x-velocity nil
- y-velocity nil mass 170000.0)
-
- * A function called copy-ship of one argument is defined that, when given a
- ship object, will create a new ship object that is a copy of the given
- one. This function is called the copier function.
-
- * One may use setf to alter the components of a ship:
-
- (setf (ship-x-position ship2) 100)
-
- This alters the x-position of ship2 to be 100. This works because
- defstruct behaves as if it generates an appropriate defsetf form for each
- access function.
-
- This simple example illustrates the power of defstruct to provide abstract
- record structures in a convenient manner. defstruct has many other features as
- well for specialized purposes.
-
- -------------------------------------------------------------------------------
-
- 19.2. How to Use Defstruct
-
- All structures are defined through the defstruct construct. A call to defstruct
- defines a new data type whose instances have named slots.
-
- [change_begin]
-
- [Macro]
-
- defstruct name-and-options [doc-string] {slot-description}+
-
- X3J13 voted in June 1988 (DEFSTRUCT-SLOTS-CONSTRAINTS-NUMBER) to allow a
- defstruct definition to have no slot-description at all; in other words, the
- occurrence of slot-description in the preceding header line would be replaced
- by slot-description.
-
- Such structure definitions are particularly useful if the :include option is
- used, perhaps with other options; for example, one can have two structures that
- are exactly alike except that they print differently (having different
- :print-function options).
-
- Implementors are encouraged to permit this simple extension as soon as
- convenient. Users, however, may wish to maximize portability of their code by
- avoiding the use of this extension unless and until it is adopted as part of
- the ANSI standard.
- [change_end]
-
- This defines a record-structure data type. A general call to defstruct looks
- like the following example.
-
- (defstruct (name option-1 option-2 ... option-m)
- doc-string
- slot-description-1
- slot-description-2
- ...
- slot-description-n)
-
- The name must be a symbol; it becomes the name of a new data type consisting of
- all instances of the structure. The function typep will accept and use this
- name as appropriate. The name is returned as the value of the defstruct form.
-
- Usually no options are needed at all. If no options are specified, then one may
- write simply name instead of (name) after the word defstruct. The syntax of
- options and the options provided are discussed in section 19.5.
-
- If the optional documentation string doc-string is present, then it is attached
- to the name as a documentation string of type structure; see documentation.
-
- Each slot-description-j is of the form
-
- (slot-name default-init slot-option-name-1 slot-option-value-1
- slot-option-name-2 slot-option-value-2 ... slot-option-name-kj
- slot-option-value-kj)
-
- Each slot-name must be a symbol; an access function is defined for each slot.
- If no options and no default-init are specified, then one may write simply
- slot-name instead of (slot-name) as the slot description.
-
- [old_change_begin]
- The default-init is a form that is evaluated each time a structure is to be
- constructed; the value is used as the initial value of the slot.
- [old_change_end]
-
- [change_begin]
- X3J13 voted in October 1988 (DEFSTRUCT-DEFAULT-VALUE-EVALUATION) to clarify
- that a default-init form is evaluated only if the corresponding argument is not
- supplied to the constructor function. The preceding sentence should therefore
- read as follows:
-
- The default-init is a form that is evaluated each time its value is to be used
- as the initial value of the slot.
- [change_end]
-
- If no default-init is specified, then the initial contents of the slot are
- undefined and implementation-dependent. The available slot-options are
- described in section 19.4.
-
- -------------------------------------------------------------------------------
- Compatibility note: Slot-options are not currently provided in Lisp Machine
- Lisp, but this is an upward-compatible extension.
- -------------------------------------------------------------------------------
-
- [change_begin]
- X3J13 voted in January 1989 (DEFSTRUCT-SLOTS-CONSTRAINTS-NAME) to specify
- that it is an error for two slots to have the same name; more precisely, no two
- slots may have names for whose print names string= would be true. Under this
- interpretation
-
- (defstruct lotsa-slots slot slot)
-
- obviously is incorrect but the following one is also in error, even assuming
- that the symbols coin:slot and blot:slot really are distinct (non-eql) symbols:
-
- (defstruct no-dice coin:slot blot:slot)
-
- To illustrate another case, the first defstruct form below is correct, but the
- second one is in error.
-
- (defstruct one-slot slot) (defstruct (two-slots (:include one-slot)) slot)
-
- -------------------------------------------------------------------------------
- Rationale: Print names are the criterion for slot-names being the same, rather
- than the symbols themselves, because defstruct constructs names of accessor
- functions from the print names and interns the resulting new names in the
- current package.
- -------------------------------------------------------------------------------
-
- X3J13 recommended that expanding a defstruct form violating this restriction
- should signal an error and noted, with an eye to the Common Lisp Object System
- (CLOS) , that the restriction applies only to the operation of the defstruct
- macro as such and not to the structure-class or structures defined with
- defclass.
-
- X3J13 voted in March 1989 (DEFINING-MACROS-NON-TOP-LEVEL) to clarify that,
- while defining forms normally appear at top level, it is meaningful to place
- them in non-top-level contexts; defstruct must treat slot default-init forms
- and any initialization forms within the specification of a by-position
- constructor function as occurring within the enclosing lexical environment, not
- within the global environment.
- [change_end]
-
- defstruct not only defines an access function for each slot, but also arranges
- for setf to work properly on such access functions, defines a predicate named
- name-p, defines a constructor function named make-name, and defines a copier
- function named copy-name. All names of automatically created functions are
- interned in whatever package is current at the time the defstruct form is
- processed (see *package*). Also, all such functions may be declared inline at
- the discretion of the implementation to improve efficiency; if you do not want
- some function declared inline, follow the defstruct form with a notinline
- declaration to override any automatic inline declaration.
-
- [change_begin]
- X3J13 voted in January 1989 (DEFSTRUCT-REDEFINITION) to specify that the
- results of redefining a defstruct structure (that is, evaluating more than one
- defstruct structure for the same name) are undefined.
-
- The problem is that if instances have been created under the old definition and
- then remain accessible after the new definition has been evaluated, the
- accessors and other functions for the new definition may be incompatible with
- the old instances. Conversely, functions associated with the old definition may
- have been declared inline and compiled into code that remains accessible after
- the new definition has been evaluated; such code may be incompatible with the
- new instances.
-
- In practice this restriction affects the development and debugging process
- rather than production runs of fully developed code. The defstruct feature is
- intended to provide ``the most efficient'' structure class. CLOS classes
- defined by defclass allow much more flexible structures to be defined and
- redefined.
-
- Programming environments are allowed and encouraged to permit defstruct
- redefinition, perhaps with warning messages about possible interactions with
- other parts of the programming environment or memory state. It is beyond the
- scope of the Common Lisp language standard to define those interactions except
- to note that they are not portable.
- [change_end]
-
- -------------------------------------------------------------------------------
-
- 19.3. Using the Automatically Defined Constructor Function
-
- After you have defined a new structure with defstruct, you can create instances
- of this structure by using the constructor function. By default, defstruct
- defines this function automatically. For a structure named foo, the constructor
- function is normally named make-foo; you can specify a different name by giving
- it as the argument to the :constructor option, or specify that you don't want a
- normal constructor function at all by using nil as the argument (in which case
- one or more ``by-position'' constructors should be requested; see section
- 19.6).
-
- A call to a constructor function, in general, has the form
-
- (name-of-constructor-function
- slot-keyword-1 form-1
- slot-keyword-2 form-2
- ...)
-
- All arguments are keyword arguments. Each slot-keyword should be a keyword
- whose name matches the name of a slot of the structure (defstruct determines
- the possible keywords simply by interning each slot-name in the keyword
- package). All the keywords and forms are evaluated. In short, it is just as if
- the constructor function took all its arguments as &key parameters. For
- example, the ship structure shown in section 19.1 has a constructor function
- that takes arguments roughly as if its definition were
-
- (defun make-ship (&key x-position y-position
- x-velocity y-velocity mass)
- ...)
-
- If slot-keyword-j names a slot, then that element of the created structure
- will be initialized to the value of form-j. If no pair slot-keyword-j and
- form-j is present for a given slot, then the slot will be initialized by
- evaluating the default-init form specified for that slot in the call to
- defstruct. (In other words, the initialization specified in the defstruct
- defers to any specified in a call to the constructor function.) If the default
- initialization form is used, it is evaluated at construction time, but in the
- lexical environment of the defstruct form in which it appeared. If the
- defstruct itself also did not specify any initialization, the element's initial
- value is undefined. You should always specify the initialization, either in the
- defstruct or in the call to the constructor function, if you care about the
- initial value of the slot.
-
- Each initialization form specified for a defstruct component, when used by the
- constructor function for an otherwise unspecified component, is re-evaluated on
- every call to the constructor function. It is as if the initialization forms
- were used as init forms for the keyword parameters of the constructor function.
- For example, if the form (gensym) were used as an initialization form, either
- in the constructor-function call or as the default initialization form in the
- defstruct form, then every call to the constructor function would call gensym
- once to generate a new symbol.
-
- [change_begin]
- X3J13 voted in October 1988 (DEFSTRUCT-DEFAULT-VALUE-EVALUATION) to clarify
- that the default value in a defstruct slot is not evaluated unless it is needed
- in the creation of a particular structure instance. If it is never needed,
- there can be no type-mismatch error, even if the type of the slot is specified,
- and no warning should be issued.
-
- For example, in the following sequence only the last form is in error.
-
- (defstruct person (name .007 :type string))
-
- (make-person :name "James")
-
- (make-person) ;Error to give name the value .007
-
- [change_end]
-
- -------------------------------------------------------------------------------
-
- 19.4. Defstruct Slot-Options
-
- Each slot-description in a defstruct form may specify one or more slot-options.
- A slot-option consists of a pair of a keyword and a value (which is not a form
- to be evaluated, but the value itself). For example:
-
- (defstruct ship
- (x-position 0.0 :type short-float)
- (y-position 0.0 :type short-float)
- (x-velocity 0.0 :type short-float)
- (y-velocity 0.0 :type short-float)
- (mass *default-ship-mass* :type short-float :read-only t))
-
- This specifies that each slot will always contain a short-format floating-point
- number, and that the last slot may not be altered once a ship is constructed.
-
- The available slot-options are as follows.
-
- :type
- The option :type type specifies that the contents of the slot will always
- be of the specified data type. This is entirely analogous to the
- declaration of a variable or function; indeed, it effectively declares the
- result type of the access function. An implementation may or may not
- choose to check the type of the new object when initializing or assigning
- to a slot. Note that the argument form type is not evaluated; it must be a
- valid type specifier.
-
- :read-only
- The option :read-only x, where x is not nil, specifies that this slot may
- not be altered; it will always contain the value specified at construction
- time. setf will not accept the access function for this slot. If x is nil,
- this slot-option has no effect. Note that the argument form x is not
- evaluated.
-
- Note that it is impossible to specify a slot-option unless a default value is
- specified first.
-
- -------------------------------------------------------------------------------
-
- 19.5. Defstruct Options
-
- The preceding description of defstruct is all that the average user will need
- (or want) to know in order to use structures. The remainder of this chapter
- discusses more complex features of the defstruct facility.
-
- This section explains each of the options that can be given to defstruct. A
- defstruct option may be either a keyword or a list of a keyword and arguments
- for that keyword. (Note that the syntax for defstruct options differs from the
- pair syntax used for slot-options. No part of any of these options is
- evaluated.)
-
- :conc-name
- This provides for automatic prefixing of names of access functions. It is
- conventional to begin the names of all the access functions of a structure
- with a specific prefix, the name of the structure followed by a hyphen.
- This is the default behavior.
-
- The argument to the :conc-name option specifies an alternative prefix to
- be used. (If a hyphen is to be used as a separator, it must be specified
- as part of the prefix.) If nil is specified as an argument, then no prefix
- is used; then the names of the access functions are the same as the
- slot-names, and it is up to the user to name the slots reasonably.
-
- Note that no matter what is specified for :conc-name, with a constructor
- function one uses slot keywords that match the slot-names, with no prefix
- attached. On the other hand, one uses the access-function name when using
- setf. Here is an example:
-
- (defstruct door knob-color width material)
- (setq my-door
- (make-door :knob-color 'red :width 5.0))
- (door-width my-door) => 5.0
- (setf (door-width my-door) 43.7)
- (door-width my-door) => 43.7
- (door-knob-color my-door) => red
-
- :constructor
- This option takes one argument, a symbol, which specifies the name of the
- constructor function. If the argument is not provided or if the option
- itself is not provided, the name of the constructor is produced by
- concatenating the string "MAKE-" and the name of the structure, putting
- the name in whatever package is current at the time the defstruct form is
- processed (see *package*). If the argument is provided and is nil, no
- constructor function is defined.
-
- This option actually has a more general syntax that is explained in
- section 19.6.
-
- :copier
- This option takes one argument, a symbol, which specifies the name of the
- copier function. If the argument is not provided or if the option itself
- is not provided, the name of the copier is produced by concatenating the
- string "COPY-" and the name of the structure, putting the name in whatever
- package is current at the time the defstruct form is processed (see
- *package*). If the argument is provided and is nil, no copier function is
- defined.
-
- The automatically defined copier function simply makes a new structure and
- transfers all components verbatim from the argument into the newly created
- structure. No attempt is made to make copies of the components.
- Corresponding components of the old and new structures will therefore be
- eql.
-
- :predicate
- This option takes one argument, which specifies the name of the type
- predicate. If the argument is not provided or if the option itself is not
- provided, the name of the predicate is made by concatenating the name of
- the structure to the string "-P", putting the name in whatever package is
- current at the time the defstruct form is processed (see *package*). If
- the argument is provided and is nil, no predicate is defined. A predicate
- can be defined only if the structure is ``named''; if the :type option is
- specified and the :named option is not specified, then the :predicate
- option must either be unspecified or have the value nil.
-
- :include
- This option is used for building a new structure definition as an
- extension of an old structure definition. As an example, suppose you have
- a structure called person that looks like this:
-
- (defstruct person name age sex)
-
- Now suppose you want to make a new structure to represent an astronaut.
- Since astronauts are people too, you would like them also to have the
- attributes of name, age, and sex, and you would like Lisp functions that
- operate on person structures to operate just as well on astronaut
- structures. You can do this by defining astronaut with the :include
- option, as follows:
-
- (defstruct (astronaut (:include person)
- (:conc-name astro-))
- helmet-size
- (favorite-beverage 'tang))
-
- The :include option causes the structure being defined to have the same
- slots as the included structure. This is done in such a way that the
- access functions for the included structure will also work on the
- structure being defined. In this example, an astronaut will therefore have
- five slots: the three defined in person and the two defined in astronaut
- itself. The access functions defined by the person structure can be
- applied to instances of the astronaut structure, and they will work
- correctly. Moreover, astronaut will have its own access functions for
- components defined by the person structure. The following examples
- illustrate how you can use astronaut structures:
-
- (setq x (make-astronaut :name 'buzz
- :age 45
- :sex t
- :helmet-size 17.5))
-
- (person-name x) => buzz
- (astro-name x) => buzz
-
- (astro-favorite-beverage x) => tang
-
- The difference between the access functions person-name and astro-name is
- that person-name may be correctly applied to any person, including an
- astronaut, while astro-name may be correctly applied only to an astronaut.
- (An implementation may or may not check for incorrect use of access
- functions.)
-
- At most one :include option may be specified in a single defstruct form.
- The argument to the :include option is required and must be the name of
- some previously defined structure. If the structure being defined has no
- :type option, then the included structure must also have had no :type
- option specified for it. If the structure being defined has a :type
- option, then the included structure must have been declared with a :type
- option specifying the same representation type.
-
- If no :type option is involved, then the structure name of the including
- structure definition becomes the name of a data type, of course, and
- therefore a valid type specifier recognizable by typep; moreover, it
- becomes a subtype of the included structure. In the above example,
- astronaut is a subtype of person; hence
-
- (typep (make-astronaut) 'person)
-
- is true, indicating that all operations on persons will also work on
- astronauts.
-
- The following is an advanced feature of the :include option. Sometimes,
- when one structure includes another, the default values or slot-options
- for the slots that came from the included structure are not what you want.
- The new structure can specify default values or slot-options for the
- included slots different from those the included structure specifies, by
- giving the :include option as
-
- (:include name slot-description-1 slot-description-2 ...)
-
- Each slot-description-j must have a slot-name or slot-keyword that is the
- same as that of some slot in the included structure. If slot-description-j
- has no default-init, then in the new structure the slot will have no
- initial value. Otherwise its initial value form will be replaced by the
- default-init in slot-description-j. A normally writable slot may be made
- read-only. If a slot is read-only in the included structure, then it must
- also be so in the including structure. If a type is specified for a slot,
- it must be the same as, or a subtype of, the type specified in the
- included structure. If it is a strict subtype, the implementation may or
- may not choose to error-check assignments.
-
- For example, if we had wanted to define astronaut so that the default age
- for an astronaut is 45, then we could have said:
-
- (defstruct (astronaut (:include person (age 45)))
- helmet-size
- (favorite-beverage 'tang))
-
- [change_begin]
-
- X3J13 voted in June 1988 (DATA-TYPES-HIERARCHY-UNDERSPECIFIED) to
- require any structure type created by defstruct (or defclass) to be
- disjoint from any of the types cons, symbol, array, number, character,
- hash-table, readtable, package, pathname, stream, and random-state. A
- consequence of this requirement is that it is an error to specify any of
- these types, or any of their subtypes, to the defstruct :include option.
- (The first edition said nothing explicitly about this. Inasmuch as using
- such a type with the :include option was not defined to work, one might
- argue that such use was an error in Common Lisp as defined by the first
- edition.)
-
- [change_end]
-
- :print-function
- This option may be used only if the :type option is not specified. The
- argument to the :print-function option should be a function of three
- arguments, in a form acceptable to the function special form, to be used
- to print structures of this type. When a structure of this type is to be
- printed, the function is called on three arguments: the structure to be
- printed, a stream to print to, and an integer indicating the current depth
- (to be compared against *print-level*). The printing function should
- observe the values of such printer-control variables as *print-escape* and
- *print-pretty*.
-
- If the :print-function option is not specified and the :type option also
- not specified, then a default printing function is provided for the
- structure that will print out all its slots using #S syntax (see section
- 22.1.4).
-
- [change_begin]
-
- X3J13 voted in January 1989 (PRINT-CIRCLE-STRUCTURE) to specify that
- user-defined printing functions for the defstruct :print-function option
- may print objects to the supplied stream using write, print1, princ,
- format, or print-object and expect circularities to be detected and
- printed using #n# syntax (when *print-circle* is non-nil, of course). See
- *print-circle*.
-
- X3J13 voted in January 1989 (DEFSTRUCT-PRINT-FUNCTION-INHERITANCE) to
- clarify that if the :print-function option is not specified but the
- :include option is specified, then the print function is inherited from
- the included structure type. Thus, for example, an astronaut will be
- printed by the same printing function that is used for person.
-
- X3J13 in the same vote extended the print-function option as follows: If
- the print-function option is specified but with no argument, then the
- standard default printing function (that uses #S syntax) will be used.
- This provides a means of overriding the inheritance rule. For example, if
- person and astronaut had been defined as
-
- (defstruct (person
- (:print-function ;Special print function
- (lambda (p s k)
- (format s "<~A, age ~D>"
- (person-name p)
- (person-age p)))))
- name age sex)
-
- (defstruct (astronaut
- (:include person)
- (:conc-name astro-)
- (:print-function)) ;Use default print function
- helmet-size
- (favorite-beverage 'tang))
-
- then an ordinary person would be printed as ``<Joe Schmoe, age 27>'' but
- an astronaut would be printed as, for example,
-
- #S(ASTRONAUT NAME BUZZ AGE 45 SEX T
- HELMET-SIZE 17.5 FAVORITE-BEVERAGE TANG)
-
- using the default #S syntax (yuk).
-
- [change_end]
-
- These changes make the behavior of defstruct with respect to the :include
- option a bit more like the behavior of classes in CLOS.
-
- :type
- The :type option explicitly specifies the representation to be used for
- the structure. It takes one argument, which must be one of the types
- enumerated below.
-
- Specifying this option has the effect of forcing a specific representation
- and of forcing the components to be stored in the order specified in the
- defstruct form in corresponding successive elements of the specified
- representation. It also prevents the structure name from becoming a valid
- type specifier recognizable by typep (see section 19.7).
-
- Normally this option is not specified, in which case the structure is
- represented in an implementation-dependent manner.
-
- vector
- This produces the same result as specifying (vector t). The
- structure is represented as a general vector, storing
- components as vector elements. The first component is
- vector element 1 if the structure is :named, and element 0
- otherwise.
-
- (vector element-type)
- The structure is represented as a (possibly specialized)
- vector, storing components as vector elements. Every
- component must be of a type that can be stored in a vector
- of the type specified. The first component is vector
- element 1 if the structure is :named, and element 0
- otherwise. The structure may be :named only if the type
- symbol is a subtype of the specified element-type.
-
- list
- The structure is represented as a list. The first component
- is the cadr if the structure is :named, and the car if it
- is :unnamed.
-
- :named
- The :named option specifies that the structure is ``named''; this option
- takes no argument. If no :type option is specified, then the structure is
- always named; so this option is useful only in conjunction with the :type
- option. See section 19.7 for a further description of this option.
-
- :initial-offset
- This allows you to tell defstruct to skip over a certain number of slots
- before it starts allocating the slots described in the body. This option
- requires an argument, a non-negative integer, which is the number of slots
- you want defstruct to skip. The :initial-offset option may be used only if
- the :type option is also specified. See section 19.7.3 for a further
- description of this option.
-
- -------------------------------------------------------------------------------
-
- 19.6. By-Position Constructor Functions
-
- If the :constructor option is given as (:constructor name arglist), then
- instead of making a keyword-driven constructor function, defstruct defines a
- ``positional'' constructor function, taking arguments whose meaning is
- determined by the argument's position rather than by a keyword. The arglist is
- used to describe what the arguments to the constructor will be. In the simplest
- case something like (:constructor make-foo (a b c)) defines make-foo to be a
- three-argument constructor function whose arguments are used to initialize the
- slots named a, b, and c.
-
- In addition, the keywords &optional, &rest, and &aux are recognized in the
- argument list. They work in the way you might expect, but there are a few fine
- points worthy of explanation. Consider this example:
-
- (:constructor create-foo
- (a &optional b (c 'sea) &rest d &aux e (f 'eff)))
-
- This defines create-foo to be a constructor of one or more arguments. The first
- argument is used to initialize the a slot. The second argument is used to
- initialize the b slot. If there isn't any second argument, then the default
- value given in the body of the defstruct (if given) is used instead. The third
- argument is used to initialize the c slot. If there isn't any third argument,
- then the symbol sea is used instead. Any arguments following the third argument
- are collected into a list and used to initialize the d slot. If there are three
- or fewer arguments, then nil is placed in the d slot. The e slot is not
- initialized; its initial value is undefined. Finally, the f slot is initialized
- to contain the symbol eff.
-
- The actions taken in the b and e cases were carefully chosen to allow the user
- to specify all possible behaviors. Note that the &aux ``variables'' can be used
- to completely override the default initializations given in the body.
-
- With this definition, one can write
-
- (create-foo 1 2)
-
- instead of
-
- (make-foo :a 1 :b 2)
-
- and of course create-foo provides defaulting different from that of make-foo.
-
- It is permissible to use the :constructor option more than once, so that you
- can define several different constructor functions, each taking different
- parameters.
-
- Because a constructor of this type operates By Order of Arguments, it is
- sometimes known as a BOA constructor.
-
- [change_begin]
- X3J13 voted in January 1989 (DEFSTRUCT-CONSTRUCTOR-KEY-MIXTURE) to allow &key
- and &allow-other-keys in the parameter list of a ``positional'' constructor.
- The initialization of slots corresponding to keyword parameters is performed in
- the same manner as for &optional parameters. A variant of the example shown
- above illustrates this:
-
- (:constructor create-foo
- (a &optional b (c 'sea)
- &key p (q 'cue) ((:why y)) ((:you u) 'ewe)
- &aux e (f 'eff)))
-
- The treatment of slots a, b, c, e, and f is the same as in the original
- example. In addition, if there is a :p keyword argument, it is used to
- initialize the p slot; if there isn't any :p keyword argument, then the default
- value given in the body of the defstruct (if given) is used instead. Similarly,
- if there is a :q keyword argument, it is used to initialize the q slot; if
- there isn't any :q keyword argument, then the symbol cue is used instead.
-
- In order thoroughly to flog this presumably already dead horse, we further
- observe that if there is a :why keyword argument, it is used to initialize the
- y slot; otherwise the default value for slot y is used instead. Similarly, if
- there is a :you keyword argument, it is used to initialize the u slot;
- otherwise the symbol ewe is used instead.
-
- If memory serves me correctly, defstruct was included in the original design
- for Common Lisp some time before keyword arguments were approved. The failure
- of positional constructors to accept keyword arguments may well have been an
- oversight on my part; there is no logical reason to exclude them. I am grateful
- to X3J13 for rectifying this.
-
- A remaining difficulty is that the possibility of keyword arguments renders the
- term ``positional constructor'' a misnomer. Worse yet, it ruins the term ``BOA
- constructor.'' I suggest that they continue to be called BOA constructors, as I
- refuse to abandon a good pun. (I regret appearing to have more compassion for
- puns than for horses.)
-
- As part of the same vote X3J13 also changed defstruct to allow BOA constructors
- to have parameters (including supplied-p parameters) that do not correspond to
- any slot. Such parameters may be used in subsequent initialization forms in the
- parameter list. Consider this example:
-
- (defstruct (ice-cream-factory
- (:constructor fabricate-factory
- (&key (capacity 5)
- location
- (local-flavors
- (case location
- ((hawaii) '(pineapple macadamia guava))
- ((massachusetts) '(lobster baked-bean))
- ((california) '(ginger lotus avocado
- bean-sprout garlic))
- ((texas) '(jalapeno barbecue))))
- (flavors (subseq (append local-flavors
- '(vanilla
- chocolate
- strawberry
- pistachio
- maple-walnut
- peppermint))
- 0 capacity)))))
- (capacity 3)
- (flavors '(vanilla chocolate strawberry mango)))
-
- The structure type ice-cream-factory has two constructors. The standard
- constructor, make-ice-cream-factory, takes two keyword arguments named
- :capacity and :flavors. For this constructor, the default for the capacity slot
- is 3 and the default list of flavors is America's favorite threesome and a dark
- horse (not a dead one). The BOA constructor fabricate-factory accepts four
- different keyword arguments. The :capacity argument defaults to 5, and the
- :flavors argument defaults in a complicated manner based on the other three.
- The :local-flavors argument may be specified directly, or may be allowed to
- default based on the :location of the factory. Here are examples of various
- factories:
-
- (setq houston (fabricate-factory :capacity 4 :location 'texas))
- (setq cambridge (fabricate-factory :location 'massachusetts))
- (setq seattle (fabricate-factory :local-flavors '(salmon)))
- (setq wheaton (fabricate-factory :capacity 4 :location 'illinois))
- (setq pittsburgh (fabricate-factory :capacity 4))
- (setq cleveland (make-factory :capacity 4))
-
- (ice-cream-factory-flavors houston)
- => (jalapeno barbecue vanilla chocolate)
-
- (ice-cream-factory-flavors cambridge)
- => (lobster baked-bean vanilla chocolate strawberry)
-
- (ice-cream-factory-flavors seattle)
- => (salmon vanilla chocolate strawberry pistachio)
-
- (ice-cream-factory-flavors wheaton)
- => (vanilla chocolate strawberry pistachio)
-
- (ice-cream-factory-flavors pittsburgh)
- => (vanilla chocolate strawberry pistachio)
-
- (ice-cream-factory-flavors cleveland)
- => (vanilla chocolate strawberry mango)
-
- [change_end]
-
- -------------------------------------------------------------------------------
-
- 19.7. Structures of Explicitly Specified Representational Type
-
- Sometimes it is important to have explicit control over the representation of a
- structure. The :type option allows one to specify that a structure must be
- implemented in a particular way, using a list or a specific kind of vector, and
- to specify the exact allocation of structure slots to components of the
- representation. A structure may also be ``unnamed'' or ``named,'' according to
- whether the structure name is stored in (and thus recoverable from) the
- structure.
-
- -------------------------------------------------------------------------------
-
- * Unnamed Structures
- * Named Structures
- * Other Aspects of Explicitly Specified Structures
-
- -------------------------------------------------------------------------------
-
- 19.7.1. Unnamed Structures
-
- Sometimes a particular data representation is imposed by external requirements,
- and yet it is desirable to document the data format as a defstruct-style
- structure. For example, consider expressions built up from numbers, symbols,
- and binary operations such as + and *. An operation might be represented as it
- is in Lisp, as a list of the operator and the two operands. This fact can be
- expressed succinctly with defstruct in this manner:
-
- (defstruct (binop (:type list))
- (operator '? :type symbol)
- operand-1
- operand-2)
-
- This will define a constructor function make-binop and three selector
- functions, namely binop-operator, binop-operand-1, and binop-operand-2. (It
- will not, however, define a predicate binop-p, for reasons explained below.)
-
- The effect of make-binop is simply to construct a list of length 3:
-
- (make-binop :operator '+ :operand-1 'x :operand-2 5)
- => (+ x 5)
-
- (make-binop :operand-2 4 :operator '*)
- => (* nil 4)
-
- It is just like the function list except that it takes keyword arguments and
- performs slot defaulting appropriate to the binop conceptual data type.
- Similarly, the selector functions binop-operator, binop-operand-1, and
- binop-operand-2 are essentially equivalent to car, cadr, and caddr,
- respectively. (They might not be completely equivalent because, for example, an
- implementation would be justified in adding error-checking code to ensure that
- the argument to each selector function is a length-3 list.)
-
- We speak of binop as being a ``conceptual'' data type because binop is not made
- a part of the Common Lisp type system. The predicate typep will not recognize
- binop as a type specifier, and type-of will return list when given a binop
- structure. Indeed, there is no way to distinguish a data structure constructed
- by make-binop from any other list that happens to have the correct structure.
-
- There is not even any way to recover the structure name binop from a structure
- created by make-binop. This can be done, however, if the structure is
- ``named.''
-
- -------------------------------------------------------------------------------
-
- 19.7.2. Named Structures
-
- A ``named'' structure has the property that, given an instance of the
- structure, the structure name (that names the type) can be reliably recovered.
- For structures defined with no :type option, the structure name actually
- becomes part of the Common Lisp data-type system. The function type-of, when
- applied to such a structure, will return the structure name as the type of the
- object; the predicate typep will recognize the structure name as a valid type
- specifier.
-
- For structures defined with a :type option, type-of will return a type
- specifier such as list or (vector t), depending on the type specified to the
- :type option. The structure name does not become a valid type specifier.
- However, if the :named option is also specified, then the first component of
- the structure (as created by a defstruct constructor function) will always
- contain the structure name. This allows the structure name to be recovered from
- an instance of the structure and allows a reasonable predicate for the
- conceptual type to be defined: the automatically defined name-p predicate for
- the structure operates by first checking that its argument is of the proper
- type (list, (vector t), or whatever) and then checking whether the first
- component contains the appropriate type name.
-
- Consider the binop example shown above, modified only to include the :named
- option:
-
- (defstruct (binop (:type list) :named)
- (operator '? :type symbol)
- operand-1
- operand-2)
-
- As before, this will define a constructor function make-binop and three
- selector functions binop-operator, binop-operand-1, and binop-operand-2. It
- will also define a predicate binop-p.
-
- The effect of make-binop is now to construct a list of length 4:
-
- (make-binop :operator '+ :operand-1 'x :operand-2 5)
- => (binop + x 5)
-
- (make-binop :operand-2 4 :operator '*)
- => (binop * nil 4)
-
- The structure has the same layout as before except that the structure name
- binop is included as the first list element. The selector functions
- binop-operator, binop-operand-1, and binop-operand-2 are essentially equivalent
- to cadr, caddr, and cadddr, respectively. The predicate binop-p is more or less
- equivalent to the following definition.
-
- (defun binop-p (x)
- (and (consp x) (eq (car x) 'binop)))
-
- The name binop is still not a valid type specifier recognizable to typep, but
- at least there is a way of distinguishing binop structures from other similarly
- defined structures.
-
- -------------------------------------------------------------------------------
-
- 19.7.3. Other Aspects of Explicitly Specified Structures
-
- The :initial-offset option allows one to specify that slots be allocated
- beginning at a representational element other than the first. For example, the
- form
-
- (defstruct (binop (:type list) (:initial-offset 2))
- (operator '? :type symbol)
- operand-1
- operand-2)
-
- would result in the following behavior for make-binop:
-
- (make-binop :operator '+ :operand-1 'x :operand-2 5)
- => (nil nil + x 5)
- (make-binop :operand-2 4 :operator '*)
- => (nil nil * nil 4)
-
- The selectors binop-operator, binop-operand-1, and binop-operand-2 would be
- essentially equivalent to caddr, cadddr, and car of cddddr, respectively.
- Similarly, the form
-
- (defstruct (binop (:type list) :named (:initial-offset 2))
- (operator '? :type symbol)
- operand-1
- operand-2)
-
- would result in the following behavior for make-binop:
-
- (make-binop :operator '+ :operand-1 'x :operand-2 5)
- => (nil nil binop + x 5)
-
- (make-binop :operand-2 4 :operator '*)
- => (nil nil binop * nil 4)
-
- If the :include is used with the :type option, then the effect is first to skip
- over as many representation elements as needed to represent the included
- structure, then to skip over any additional elements specified by the
- :initial-offset option, and then to begin allocation of elements from that
- point. For example:
-
- (defstruct (binop (:type list) :named (:initial-offset 2))
- (operator '? :type symbol)
- operand-1
- operand-2)
-
- (defstruct (annotated-binop (:type list)
- (:initial-offset 3)
- (:include binop))
- commutative associative identity)
-
- (make-annotated-binop :operator '*
- :operand-1 'x
- :operand-2 5
- :commutative t
- :associative t
- :identity 1)
- => (nil nil binop * x 5 nil nil nil t t 1)
-
- The first two nil elements stem from the :initial-offset of 2 in the definition
- of binop. The next four elements contain the structure name and three slots for
- binop. The next three nil elements stem from the :initial-offset of 3 in the
- definition of annotated-binop. The last three list elements contain the
- additional slots for an annotated-binop.
-
- -------------------------------------------------------------------------------
-
-
-
-